package com.plugin.sproutcore.core.launch;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.core.model.RuntimeProcess;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;

import com.plugin.sproutcore.lang.ProcessFactory;
import com.plugin.sproutcore.util.ConsoleUtil;

/**
 * Extends the functionality of <code>RuntimeProcess</code> to make the called
 * process, sc-server terminate successfully on a windows machine. The process
 * uses the PID of the sc-server process to terminate it. The PID is obtained
 * through the output of the sc-server process. You can only terminate this 
 * process once the PID is obtained. The PID is terminated using windows 
 * taskkill native command. Taskkill is run using <code>ProcessFactory</code>.
 */
public class SproutCoreWinRuntimeProcess extends RuntimeProcess
{
	/**
	 * The PID of the process.
	 */
	private Integer _pid = null;
	
	/**
	 * Is this object ready for user interaction? Set to true once the PID is
	 * found.
	 */
	private boolean _isInitialised = false;

	/**
	 * Listen to the process's output. Try to capture the PID of the process.
	 */
	private final IStreamListener _OutputListener;
	
	/**
	 * Regular expression used to find the PID in the process output stream.
	 */
	private static final Pattern REGEXP = Pattern.compile("pid=(\\d*)");
	
	/**
	 * @see RuntimeProcess#RuntimeProcess(ILaunch, Process, String, Map)
	 */
	public SproutCoreWinRuntimeProcess(ILaunch launch, Process process,
			String name, Map<String, String> attributes) 
	{
		super(launch, process, name, attributes);
		
		// watch the outputStream...
		_OutputListener = new IStreamListener() 
		{
			public void streamAppended(String text, IStreamMonitor monitor) 
			{
				findPid(text);
				// Don't need to listen anymore, we have found the PID! 
				if (_pid != null) {
					monitor.removeListener(_OutputListener);
				}
			}
		};
		getStreamsProxy().getErrorStreamMonitor().addListener(_OutputListener);
		getStreamsProxy().getOutputStreamMonitor().addListener(_OutputListener);
	}
	
	/**
	 * Find the PID in a body of text. When the PID is found it is set and
	 * the state of the process is set to initialised.
	 * 
	 * @param text
	 * The text to look at.
	 */
	private void findPid(String text)
	{
		// look for the PID details, so the process can be exited on a windows
		// machine.
		Matcher matcher = REGEXP.matcher(text);
		if (matcher.find()) 
		{
			// is there a better way than replace?
			String pid = matcher.group().replaceFirst("pid=", "");
			if (pid != null)
			{
				_pid = Integer.parseInt(pid);
				_isInitialised = true;
			}
		}
	}
	
	/**
	 * @return The PID of the running sc-server process.
	 */
	public Integer getPID()
	{
		return _pid;
	}
	/**
	 * @return Has the PID been found?
	 */
	public boolean hasPID()
	{
		return (getPID() != null) && _isInitialised;
	}
	
	/**
	 * @see RuntimeProcess#terminate()
	 */
	@Override
	public void terminate() throws DebugException 
	{
		if (_isInitialised)
		{
			if (!isTerminated()) 
			{		
				if (hasPID()) 
				{
					killPid(getPID());
					// now you can terminate the process
				}
			}
			super.terminate();
		}
	}

	/**
	 * @see RuntimeProcess#terminated()
	 */
	@Override
	protected void terminated() 
	{
		// If the pid file still exists, that means the process is still running
		if (hasPID()) {
			super.terminated();
		}
	}
	
	/**
	 * Will terminate sc-server process using the PID.
	 * 
	 * @param pid
	 * The PID to use to terminate the process. If it is invalid then a 
	 * DebugException is thrown.
	 * 
	 * @throws DebugException 
	 */
	private void killPid(final Integer pid) throws DebugException
	{
		try 
		{
			if (pid != null) 
			{
				// Found the pid! Run a system command to remove the process.
				List<String> command = new ArrayList<String>();
				command.add("kill");
				command.add(pid.toString());
				
				ProcessBuilder builder = ProcessFactory.generate(command, null);
				Process process = builder.start();
				
				MessageConsole console = ConsoleUtil.getConsole();
				monitorStream(process.getErrorStream(), console);
				monitorStream(process.getInputStream(), console);
				
				process.waitFor();
			} 
		} 
		catch (Exception e) 
		{
			IStatus status = new Status(IStatus.ERROR, 
					DebugPlugin.getUniqueIdentifier(), 
					DebugException.TARGET_REQUEST_FAILED, 
					e.getMessage(), 
					null);		 
			throw new DebugException(status);
		}
	}

	/**
	 * Send information about the termination to the default SproutCore console
	 * display. Useful to know what went wrong if something did.
	 * 
	 * @param is
	 * @param console
	 * @throws IOException
	 */
	private  void monitorStream(final InputStream is, MessageConsole console) 
	throws IOException
	{
		final InputStreamReader isr = new InputStreamReader(is);
		final BufferedReader br = new BufferedReader(isr);
		String line=null;
		MessageConsoleStream consoleStream = console.newMessageStream();
		while ( (line = br.readLine()) != null)
		{
			consoleStream.println(line);
		}
		consoleStream.close();
	}
	
	
	
	
}
